package cn.lg.soar.core.annotations.constraints;

import cn.lg.soar.common.data.dict.IDict;
import cn.lg.soar.common.util.data.StringUtil;

import javax.validation.Constraint;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import javax.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Objects;
import java.util.function.Function;

/**
 * 枚举验证，只允许枚举范围内的值
 * @author luguoxiang
 * @date 2021/9/28
 * 开源项目：https://gitee.com/lgx1992/lg-soar 求star！请给我star！请帮我点个star！
 */
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(value = RetentionPolicy.RUNTIME)
@Constraint(validatedBy= Enum.Validator.class)
public @interface Enum {

    /**
     * 值类型；
     * 指定是“enums”内元素的类型；
     * 若是枚举类型则“enums”失效，例（推荐）：@Enum(value = UserTypeEnum.class, enumMethod = "getValue")；
     * @return
     */
    Class<?> value();

    /**
     * 配置允许值；
     * 若”value“是枚举，则该配置无效；
     * 支持数租方式配置，例：@Enum(value = Integer.class, enums = { "1", "2" })；
     * 支持“,”分隔方式配置，例：@Enum(value = Integer.class, enums = "1,2")；
     * 不支持混用：反例：@Enum(value = Integer.class, enums = { "1,2", "3", "4" })；
     * @return
     */
    String[] enums() default "";

    /**
     * 获取枚举值的方法；
     * “value”是枚举时用以指定代表枚举值的方法
     * @return
     */
    String enumMethod() default "";

    String message() default "值不合法";

    boolean required() default true;

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

    enum Empty{}

    /**
     * 校验器
     */
    class Validator implements ConstraintValidator<Enum, Object> {

        private boolean required;
        private Object[] enums;

        @Override
        public void initialize(Enum ca) {
            this.required = ca.required();
            Class<?> value = ca.value();
            if (value.isEnum()) {
                Method method;
                try {
                    if (!StringUtil.isBlank(ca.enumMethod())) {
                        method = value.getMethod(ca.enumMethod());
                    } else if (IDict.class.isAssignableFrom(ca.value())) {
                        method = value.getMethod("getValue");
                    } else {
                        method = value.getMethod("name");
                    }

                    Object[] enumConstants = value.getEnumConstants();
                    this.enums = new Object[enumConstants.length];
                    for (int i = 0; i < enumConstants.length; i++) {
                        this.enums[i] = method.invoke(enumConstants[i]);
                    }
                } catch (InvocationTargetException | NoSuchMethodException | IllegalAccessException e) {
                    throw new RuntimeException(e);
                }
                return;
            }

            Function<String, ?> function;
            switch (value.getName()) {
                case "java.lang.String": function = str -> str;break;
                case "java.lang.Long": function = Long::valueOf; break;
                case "java.lang.Integer": function = Integer::valueOf; break;
                case "java.lang.Short": function = Short::valueOf; break;
                case "java.lang.Byte": function = Byte::valueOf; break;
                case "java.lang.Double": function = Double::valueOf; break;
                case "java.lang.Float": function = Float::valueOf; break;
                case "java.lang.Boolean": function = Boolean::valueOf; break;
                case "java.math.BigDecimal": function = BigDecimal::new; break;
                case "java.math.BigInteger": function = BigInteger::new; break;
                default: throw new RuntimeException("不支持的类型");
            }
            String[] enums = ca.enums();
            if (enums.length == 0 || (enums.length == 1 && StringUtil.isBlank(enums[0]))) {
                throw new RuntimeException("不是枚举类型则必须配置”enums“");
            }
            if (enums.length == 1) {
                enums = enums[0].split(",");
            }
            this.enums = new Object[enums.length];
            for (int i = 0; i < enums.length; i++) {
                this.enums[i] = function.apply(enums[i]);
            }
        }

        @Override
        public boolean isValid(Object s, ConstraintValidatorContext constraintValidatorContext) {
            if (s == null) {
                return !this.required;
            }
            for (Object e : this.enums) {
                if (Objects.equals(s, e)) {
                    return true;
                }
            }
            return false;
        }

    }

}
